package dgo.tilemap;
import dgo.interval.*;
import dgo.Callback;
import dgo.MyLib;
import java.util.Vector;
import starruby.Texture;
import java.util.Enumeration;

public class MapCharacter {
  public boolean passEvent, passCharacters, passTile = false;
  public boolean visible, dirFix, moveAnime, stayAnime;
  public Map map;
  public byte sx, sy, mapX, mapY, layer, wait, frameNo, dirX, dirY, hitX, hitY, offsetX, offsetY;
  public int alpha;
  private byte speed, speedType;
  public int screenX, screenY;
  public boolean allowDiagonalMovement, pause;
  public CharacterChipset chipset;
  private byte _a;
  private IntervalRunner _moveRunner, _waitRunner;
  public Vector commands, checked, touched;
  private int _walkCounter, _walkAnimeDir;

  public void setSpeedType(byte index){
    speedType = index;
    if(speedType < 0){speedType = 0;}
    if(speedType >= CharacterSpeedType.max() ){speedType = CharacterSpeedType.max();}
    speed = CharacterSpeedType.get(speedType);
  }
  public byte getSpeedType(){
    return speedType;
  }
  
  public MapCharacter(Map map, CharacterChipset chipset, int sx, int sy, int mapX, int mapY, boolean allowDiagonalMovement){
    
    _moveRunner = new IntervalRunner();
    _waitRunner = new IntervalRunner();
    
    this.map = map;
    this.sx = (byte)sx;
    this.sy = (byte)sy;
    this.mapX = (byte)mapX;
    this.mapY =(byte) mapY;
    
    this.allowDiagonalMovement = allowDiagonalMovement;
    
    speed = 2;
    layer = 1;
    wait = 0;
    visible = true;
    _a = 1;
    dirX = 0;
    dirY = -1;
    
    commands = new Vector();
    checked = new Vector();
    touched = new Vector();
    
    pause = false;
    frameNo = 0;
    passCharacters = true;
    passTile = true;
    passEvent = true;
    dirFix = false;
    stayAnime = false;
    moveAnime = true;
    
    _walkCounter = 0;
    _walkAnimeDir = 1;
    setChip(chipset, sx, sy);
  }
  /*
   * Set character chipset
   */
  public void setChip(CharacterChipset chipset, int sx, int sy){
    if(chipset == null){
      this.chipset = new CharacterChipset(null, null, 16, 16, 1, 1);
    }else{
      this.chipset = chipset;
    }
    
    this.sx = (byte)sx;
    this.sy = (byte)sy;
    resetPosition();
  }
  
  public int width(){
    return this.chipset.sizeX;
  } 
  
  public int height(){
    return this.chipset.sizeY;
  }

 
  public byte getCurrentDir(){
    return this.getDir(this.dirX, this.dirY);
  }
  
  public byte getDir(int dx, int dy){
    if(dirX > 0) return 1;
    if(dirX < 0) return 3;
    if(dirY > 0) return 2;
    if(dirY < 0) return 0;
    return 0;
  }
  
  public void setDir(int dir){
    this.dirX = MyLib.extractDirX(dir);
    this.dirY = MyLib.extractDirY(dir);
  }
  
  // Character's move on the map
  private Interval moveInterval(int time, int vx, int vy){

    Callback screenXMove = new Callback(this){
      public void call(int value, Object argument){
        ((MapCharacter)this.main).screenX = value;
      }
    };
    
    Callback screenYMove = new Callback(this){
      public void call(int value, Object argument){
        ((MapCharacter)this.main).screenY = value;
      }
    };
    
    return new Sequence(new Interval[]{ 
      new Parallel(new Interval[]{
        new Lerp(time, this.screenX, this.screenX + vx * time, screenXMove),
        new Lerp(time, this.screenY, this.screenY + vy * time, screenYMove)
      })
    });
  }
  
  // Character's walk
  private Interval walkInterval(int time, int vx, int vy){
    return this.moveInterval(time, vx, vy);
  }
  
  public boolean isPassable(int vx, int vy){
    int dir1 = 0;
    if(vx > 0)dir1 += CollisionType.RIGHT;
    if(vx < 0)dir1 += CollisionType.LEFT;
    if(vy > 0)dir1 += CollisionType.DOWN;
    if(vy < 0)dir1 += CollisionType.UP;
    
    int dir2 = 0;
    if(vx > 0)dir2 += CollisionType.LEFT;
    if(vx < 0)dir2 += CollisionType.RIGHT;
    if(vy > 0)dir2 += CollisionType.UP;
    if(vy < 0)dir2 += CollisionType.DOWN;
    
    if(vx != 0 && vy != 0){
      int dir3 = 0;
      if(vx > 0)dir3 += CollisionType.LEFT;
      if(vx < 0)dir3 += CollisionType.RIGHT;
      if(vy > 0)dir3 += CollisionType.DOWN;
      if(vy < 0)dir3 += CollisionType.UP;
    
      int dir4 = 0;
      if(vx > 0)dir4 += CollisionType.RIGHT;
      if(vx < 0)dir4 += CollisionType.LEFT;
      if(vy > 0)dir4 += CollisionType.UP;
      if(vy < 0)dir4 += CollisionType.DOWN;
      
      return !(map.isObstacle(this, mapX, mapY, dir1) || 
        (map.isObstacle(this, mapX + vx, mapY, dir3)) ||
        (map.isObstacle(this, mapX, mapY + vy, dir4)) ||
        (map.isObstacle(this, mapX + vx, mapY + vy, dir2))
        );
    }else{

      return !(map.isObstacle(this, mapX, mapY, dir1) || (map.isObstacle(this, mapX + vx, mapY + vy, dir2)));
    }
  }
  
  public boolean isRunning(){
    return !(_moveRunner.isDone() && _waitRunner.isDone());
  }

  /* -----------------------------
   Movment
  ----------------------------- */
  public void setWait(int wait){
   _waitRunner.setInterval(new Act(wait, null));
  }
          
  public void faceTo(MapCharacter target){
    int tx = target.mapX - mapX;
    dirX = 0;
    dirY = 0;
    if(tx > 0)dirX = 1;
    if(tx < 0)dirX = -1;
    
    int ty = target.mapY - mapY;
    if(ty > 0)dirY = 1;
    if(ty < 0)dirY = -1;

    if(Math.abs(tx) > Math.abs(ty)){
      dirY = 0;
    }else{
      dirX = 0;
    }
  }
/*------------------------------------*/
  
  public boolean isChecked(MapCharacter c){
    return checked.contains(c);
  }

  public void check(MapCharacter c){
    this.checked.addElement(c);
  }
  
  public boolean isTouched(MapCharacter c){
    return touched.contains(c);
  }

  public void touch(MapCharacter c){
    this.touched.addElement(c);
  }
  
  public void reset(){
   checked.removeAllElements();
   touched.removeAllElements();
  }

  public void resetPosition(){
    _moveRunner.clear();
    screenX = mapX * map.gridWidth - (width() % map.gridWidth) / 2 + offsetX * map.gridWidth;
    screenY = (mapY + 1) * map.gridHeight - height() + offsetY * map.gridHeight;
  }
          
  public boolean setMovement(int vx, int vy){
    if(vx == 0 && vy == 0){return false;}
    
    if(!dirFix){
      if(vy == -1){
        this.dirX = 0;
        this.dirY = -1;
      }
      if(vy == 1){
        this.dirX = 0;
        this.dirY = 1;
      }
      if(vx == -1){
        this.dirX = -1;
        this.dirY = 0;
      }
      if(vx == 1){
        this.dirX = 1;
        this.dirY = 0;
      }
    }

    this.map.removeCharacter(this.mapX, this.mapY, this);
    if(!this.isPassable(0, vy)){vy = 0;}
    if(!this.isPassable(vx, 0)){vx = 0;}
    if(!this.isPassable(vx, vy)){vx = 0; vy = 0;}

       
    if(vx == 0 && vy == 0){
      this.map.setCharacter(this.mapX, this.mapY, this);
      _moveRunner.update();
      return false;
    }else{
      this.mapX += vx;
      this.mapY += vy;
      
      Interval walkInterval = this.walkInterval(this.map.gridWidth / this.speed, vx * this.speed, vy * this.speed);

      // Action when the character touches other characters
      Callback callback = new Callback(this){
        public void call(int value, Object argument){
          MapCharacter self = (MapCharacter)this.main;
          MapCharacter t = self.map.getCharacter(self.mapX, self.mapY);
          if(t != null){t.touch(self);}
        }
      };
      
      Act touchInterval = new Act(callback);
      
      Sequence s = new Sequence(new Interval[]{walkInterval, touchInterval});
      _moveRunner.setInterval(s);
      this.map.setCharacter(this.mapX, this.mapY, this);
      _moveRunner.update();
      return true;
    }
  }

  public void animate(){
    if(speed <= 8 && (stayAnime || (!_moveRunner.isDone() && moveAnime))){
      _walkCounter += 1;
      if(_walkCounter >= (8 / speed)){
        _walkCounter = 0;
        int t = frameNo + _walkAnimeDir;
        if(t < 0 || t >= chipset.animationFrameNumber){ 
          _walkAnimeDir *= -1;
        }
        frameNo += _walkAnimeDir;
      }
    }else{
      if(moveAnime){
        if(frameNo >  chipset.defaultFrameNo){
            _walkAnimeDir = -1;
        }else{
            _walkAnimeDir = 1;
        }
        frameNo = chipset.defaultFrameNo;
        _walkCounter = 0;
      }
    }
  }

  public void update(){
    animate();
    _moveRunner.update();
    _waitRunner.update();
  }
  
  public void inputKeys(Vector keys){
    if(this.isRunning()) return;
    if(this.pause) return;
    int vx = 0, vy = 0;

    Enumeration e = keys.elements();
    while(e.hasMoreElements()){
      String key = (String)(e.nextElement());
      if(key.equals("up")){ vy = -1; }
      if(key.equals("down")){ vy = 1; }
      if(key.equals("right")){ vx = 1; }
      if(key.equals("left")){ vx = -1; }
    }
    this.setMovement(vx, vy);
  }

  public void render(Texture s, int dx, int dy){
    if(!(this.chipset != null && visible)) return;
    this.chipset.render(s, screenX + dx, screenY + dy, sx, sy, frameNo, getCurrentDir());
  }
}
